#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef WIN32
#include <Winsock2.h>
#endif
#include "ip2c.h"

void ip2c_db_version(struct ip2c_db *db)
{
	printf("Database version: %s %d.%d, %lu records, %lu IPs covered\n", db->ident, db->vers_hi, db->vers_lo, db->rec_count, db->ip_count);
}

struct ip2c_db *ip2c_db_init()
{
	struct ip2c_db *db = malloc(sizeof(struct ip2c_db));
	strncpy(db->ident, IP2C_DB_IDENT, strlen(IP2C_DB_IDENT));
	db->ip_count = 0;
	db->rec_count = 0;
	db->vers_hi = IP2C_DB_VERS_HI;
	db->vers_lo = IP2C_DB_VERS_LO;
	db->ip_s = NULL;
	db->ip_e = NULL;

	return db;
}

int ip2c_db_free(struct ip2c_db *db)
{
	free(db->ip_c);
	free(db->ip_s);
	free(db->ip_e);
	free(db);
	db = NULL;

	return 0;
}

int ip2c_db_insert(struct ip2c_db *db, const char *ip_c, unsigned long ip_s, unsigned long ip_e)
{
	db->rec_count++;

	db->ip_s = realloc(db->ip_s, (db->rec_count * sizeof(ip2c_ip)));
	db->ip_s[db->rec_count - 1] = ip_s;

	db->ip_e = realloc(db->ip_e, (db->rec_count * sizeof(ip2c_ip)));
	db->ip_e[db->rec_count - 1] = ip_e;

	db->ip_c = realloc(db->ip_c, (db->rec_count * (sizeof(ip2c_country_code) + 2)));
	strncpy(db->ip_c[db->rec_count - 1], ip_c, sizeof(ip2c_country_code));

	db->ip_count += ip_e - ip_s;

	return 0;
}

int ip2c_db_load(struct ip2c_db *db, const char *file_name)
{
	FILE *f;

	if(f = fopen(file_name, "rb"))
	{
		fread(db->ident, strlen(IP2C_DB_IDENT) + 1, 1, f);
		fread(&db->vers_hi, sizeof(db->vers_hi), 1, f);
		fread(&db->vers_lo, sizeof(db->vers_lo), 1, f);
		fread(&db->rec_count, sizeof(db->rec_count), 1, f);
		fread(&db->ip_count, sizeof(db->ip_count), 1, f);
		if(db->ip_s = malloc(sizeof(ip2c_ip) * db->rec_count))
		{
			fread(db->ip_s, sizeof(ip2c_ip), db->rec_count, f);
		} else {
			fprintf(stderr, "Cannot allocate memory(ip_s)\n");
			return 1;
		}

		if(db->ip_e = malloc(sizeof(ip2c_ip) * db->rec_count))
		{
			fread(db->ip_e, sizeof(ip2c_ip), db->rec_count, f);
		} else {
			fprintf(stderr, "Cannot allocate memory(ip_e)\n");
			return 1;
		}

		if(db->ip_c = malloc(sizeof(ip2c_country_code) * db->rec_count))
		{
			fread(db->ip_c, sizeof(ip2c_country_code), db->rec_count, f);
		} else {
			fprintf(stderr, "Cannot allocate memory(ip_c)\n");
			return 1;
		}

		fclose(f);
	} else {
		fprintf(stderr, "Cannot open file `%s`\n", file_name);
		return 1;
	}

	return 0;
}

int ip2c_db_save(struct ip2c_db *db, const char *file_name)
{
	FILE *f;

	if(f = fopen(file_name, "wb"))
	{
		fwrite(db->ident, strlen(IP2C_DB_IDENT) + 1, 1, f);
		fwrite(&db->vers_hi, sizeof(db->vers_hi), 1, f);
		fwrite(&db->vers_lo, sizeof(db->vers_lo), 1, f);
		fwrite(&db->rec_count, sizeof(db->rec_count), 1, f);
		fwrite(&db->ip_count, sizeof(db->ip_count), 1, f);
		fwrite(db->ip_s, sizeof(ip2c_ip), db->rec_count, f);
		fwrite(db->ip_e, sizeof(ip2c_ip), db->rec_count, f);
		fwrite(db->ip_c, sizeof(ip2c_country_code), db->rec_count, f);
		fclose(f);
	} else {
		fprintf(stderr, "Cannot create file `%s`\n", file_name);
		return 1;
	}

	return 0;
}

int ip2c_getcountry(const struct ip2c_db *db, const unsigned long *ip_array, const unsigned long ip_array_size, ip2c_country_code *iso_codes)
{
	unsigned long r, t;
	long found, lo, mi, hi;

	/** Loop through user IP list */
	for(r = 0; r < ip_array_size; r++)
	{
		found = 0;
		lo = 0;
		hi = db->rec_count - 1;

		/** Binary search */
		while(hi - lo > 1)
		{
			mi = (hi - lo) / 2 + lo;

			if((ip_array[r] >= db->ip_s[mi]) && (ip_array[r] <= db->ip_e[mi]))
			{
				found = mi;
				hi = mi;
			} else {
				if(ip_array[r] >= db->ip_s[mi])
				{
					hi = mi;
				} else {
					lo = mi;
				}
			}
		}

		/** If still not found: search down. Slowest part. */
		if(!found)
		{
			//printf("mi=%lu,count=%lu\n-------------------------\n", mi, db->rec_count);
			for(t = mi; t < db->rec_count; t++)
			{
				//printf("%lu\t%s,%lu,%lu\n", ip_array[r], db->ip_c[t], db->ip_s[t], db->ip_e[t]);
				if((ip_array[r] >= db->ip_s[t]) && (ip_array[r] <= db->ip_e[t]))
				{
					found = t;
					break;
				}
			}
		}

		/** Copy country codes. Need to be tested (pass only pointers?). */
		if(found)
		{
			strncpy(iso_codes[r], db->ip_c[found], sizeof(ip2c_country_code));
		} else {
			strncpy(iso_codes[r], "\0", 1);
		}
	}

	return 0;
}
